Skip to content

Conversation

@wolf31o2
Copy link
Member

@wolf31o2 wolf31o2 commented Nov 2, 2025

Summary

This PR replaces the hard-coded slot threshold for bulk blockfetch operations with the protocol-defined stability window, addressing the TODO comment in the codebase.

Changes

Core Changes

  • **Replaced hard-coded ** with dynamic calculation based on the stability window
  • Era-aware security parameter selection:
    • Byron era: Uses parameter from
    • Shelley+ eras: Uses from
  • Updated stability window calculation:
    • Byron era: stability window = (security parameter)
    • Shelley+ eras: stability window = (where f is active slots coefficient)

Files Modified

  • : Updated blockfetch slot threshold logic and epoch nonce calculation
  • : Updated block validation to use correct security parameters

Benefits

  1. Protocol Compliance: Now uses the proper Cardano protocol-defined security parameters
  2. Era Awareness: Correctly handles different security parameter sources across Byron and Shelley+ eras
  3. Network Flexibility: Automatically adapts to different network configurations (mainnet, testnet, etc.)
  4. Maintainability: Eliminates hard-coded values and TODO comments

Testing

  • Code compiles successfully
  • All existing tests pass
  • No breaking changes to existing functionality

Technical Details

The stability window is calculated differently based on the era:

  • Byron era (ID 0): Uses the parameter directly as the stability window
  • Shelley+ eras: Calculates where is the security parameter and is the active slots coefficient from genesis

This ensures that bulk blockfetch operations stop at the appropriate distance from the chain tip, as defined by the protocol's stability guarantees.

Summary by CodeRabbit

  • Bug Fixes

    • Block-fetch gating and header handling now use an era-aware stability threshold with safe defaults, reducing premature catch-up exits and clearer warnings when genesis/config data is missing or invalid.
  • Refactor

    • Epoch-nonce and stability-window logic consolidated into a single era-aware calculation with guarded behavior and fallbacks for missing or malformed genesis data.
  • Tests

    • Extensive unit tests added covering stability-window and epoch-nonce scenarios and edge cases across all eras.

Closes #591

@wolf31o2 wolf31o2 requested a review from a team as a code owner November 2, 2025 18:30
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 2, 2025

📝 Walkthrough

Walkthrough

Replaces ad-hoc and hard-coded stability-window logic with an era-aware calculation by adding func (ls *LedgerState) calculateStabilityWindow() uint64. Byron-era uses Byron genesis k (with guards); Shelley+ eras compute floor(3*k / ActiveSlotsCoeff) with validation and fallbacks to a default. handleEventChainsyncBlockHeader, calculateEpochNonce, and ledger block-processing now use this stability window for gating and blockfetch thresholds. calculateEpochNonce now errors when a nonce is absent and Shelley genesis hash is missing. Adds extensive unit tests covering epoch-nonce and stability-window behavior across eras and edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to calculateStabilityWindow() for correct formula, integer rounding, overflow/underflow guards, and default fallback semantics.
  • Verify era branching and genesis retrieval logic (Byron vs Shelley+), including nil/invalid-field handling and log paths.
  • Confirm consistent replacement of prior k usage with stabilityWindow in:
    • ledger/chainsync.go (block header handling, blockfetch threshold)
    • ledger/state.go (ledgerProcessBlocks and related logic)
    • ledger/chainsync.go (calculateEpochNonce guards)
  • Review new tests (ledger/chainsync_test.go, ledger/state_test.go) for determinism, expected-value correctness, and coverage of edge cases (missing genesis, zero/invalid ActiveSlotsCoeff, large values).

Possibly related PRs

  • fix(ledger): fix uint64 underrun #964 — Fixes a uint64 underflow in epoch/slot math and modifies calculateEpochNonce/stability-window related logic; closely related to the stability-window refactor in this PR.

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: use stability window for slot threshold' clearly and concisely summarizes the main change: replacing a hard-coded slot threshold with a dynamic stability window calculation.
Linked Issues check ✅ Passed The pull request successfully addresses issue #591 by replacing the hard-coded slot threshold with a stability-window-based calculation derived from protocol parameters, achieving era-aware security parameter selection.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue: refactoring slot threshold logic, implementing calculateStabilityWindow(), updating block header handling, and adding comprehensive unit tests for nonce and stability calculations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/use-stability-window-for-blockfetch

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8825574 and c2ab4d2.

📒 Files selected for processing (4)
  • ledger/chainsync.go (4 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ledger/chainsync.go
  • ledger/state_test.go
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.

Applied to files:

  • ledger/chainsync_test.go
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
ledger/chainsync_test.go (5)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: Analyze (go)
  • GitHub Check: nilaway
🔇 Additional comments (4)
ledger/state.go (2)

409-504: LGTM! Stability window calculation is production-ready.

The implementation correctly handles era-specific stability windows with comprehensive validation:

  • Byron era: 2k slots (consensus-spec compliant)
  • Shelley+ eras: ceiling(3k/f) with proper big-integer arithmetic
  • Graceful fallbacks for missing/invalid genesis data, negative/zero parameters, and overflow conditions
  • Clear warning logs on all error paths

All concerns from previous reviews have been addressed.


692-727: LGTM! Correct usage of dynamic stability window.

The validation gating logic properly derives the cutoff slot from the protocol-defined stability window and enables validation only for blocks within that window, replacing the previous hard-coded threshold. The debug logging clearly shows the computed window and cutoff values.

ledger/chainsync_test.go (2)

340-377: Test correctly validates fallback behavior for zero ActiveSlotsCoeff.

The test verifies that calculateStabilityWindow() returns the default threshold when ActiveSlotsCoeff is zero, which is the correct fallback behavior. Note: the test name mentions calculateEpochNonce but actually tests calculateStabilityWindow - while related, this could be clarified for future maintainers.


30-723: Excellent test coverage for epoch nonce and stability window calculations.

The test suite thoroughly validates:

  • Era-specific behavior (Byron's nil nonce vs. Shelley+ genesis-derived nonces)
  • Edge cases (negative/zero parameters, invalid genesis, missing genesis)
  • All seven Cardano eras from Byron through Conway
  • Integer arithmetic precision with fractional coefficients
  • Dynamic stability window calculation correctness
  • Proper fallback to defaults when genesis data is invalid

All tests align with the production implementation and previous review resolutions.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ledger/chainsync.go (1)

274-314: Critical: Unreachable dead code and calculation issues.

  1. Unreachable dead code: Lines 279-281 return early for Byron era (Id == 0), making the Byron-specific branches at lines 300-301 and 307-308 unreachable. These conditions will never be true since we've already returned for Byron.

  2. Same calculation issues as handleEventChainsyncBlockHeader:

    • Missing nil check for ActiveSlotsCoeff.Rat (line 312)
    • Incorrect use of .Num().Uint64() instead of proper integer division (lines 310-314)
    • Missing validation for negative SecurityParam

Apply this diff:

 	// Calculate stability window
 	byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
 	shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
 	if byronGenesis == nil || shelleyGenesis == nil {
 		return nil, errors.New("could not get genesis config")
 	}
-	var securityParam uint64
-	// Use K parameter from Byron Genesis during Byron era, SecurityParam from Shelley Genesis for later eras
-	if ls.currentEra.Id == 0 { // Byron era ID is 0
-		securityParam = uint64(byronGenesis.ProtocolConsts.K)
-	} else {
-		securityParam = uint64(shelleyGenesis.SecurityParam)
-	}
-	// Calculate stability window: 3k/f for Shelley+ eras, just k for Byron
-	var stabilityWindow uint64
-	if ls.currentEra.Id == 0 { // Byron era
-		stabilityWindow = securityParam
-	} else {
-		stabilityWindow = new(big.Rat).Quo(
-			big.NewRat(int64(3*securityParam), 1),
-			shelleyGenesis.ActiveSlotsCoeff.Rat,
-		).Num().Uint64()
-	}
+	// Note: We're already in Shelley+ era due to early return at line 279
+	// Use SecurityParam from Shelley Genesis
+	k := shelleyGenesis.SecurityParam
+	if k < 0 {
+		return nil, fmt.Errorf("security param must be non-negative: %d", k)
+	}
+	securityParam := uint64(k)
+	
+	// Calculate stability window: 3k/f for Shelley+ eras
+	if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
+		return nil, errors.New("ActiveSlotsCoeff.Rat is nil")
+	}
+	result := new(big.Rat).Quo(
+		big.NewRat(int64(3*securityParam), 1),
+		shelleyGenesis.ActiveSlotsCoeff.Rat,
+	)
+	// Proper integer division: divide numerator by denominator
+	stabilityWindow := new(big.Int).Div(result.Num(), result.Denom()).Uint64()
🧹 Nitpick comments (1)
ledger/chainsync.go (1)

143-171: Consider extracting stability window calculation to a helper function.

The stability window calculation logic (era-aware security parameter retrieval + 3k/f formula for Shelley+) is duplicated between handleEventChainsyncBlockHeader and calculateEpochNonce. This duplication increases maintenance burden and the risk of inconsistencies.

Consider creating a helper method:

// calculateStabilityWindow returns the stability window based on the current era.
// For Byron era, returns k. For Shelley+ eras, returns 3k/f.
// Returns the default threshold if genesis data is unavailable or invalid.
func (ls *LedgerState) calculateStabilityWindow() uint64 {
	byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
	shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
	if byronGenesis == nil || shelleyGenesis == nil {
		return blockfetchBatchSlotThresholdDefault
	}
	
	var securityParam uint64
	if ls.currentEra.Id == 0 { // Byron era
		securityParam = uint64(byronGenesis.ProtocolConsts.K)
		return securityParam // Byron uses k directly
	}
	
	// Shelley+ eras
	k := shelleyGenesis.SecurityParam
	if k < 0 {
		ls.config.Logger.Warn("invalid negative security parameter", "k", k)
		return blockfetchBatchSlotThresholdDefault
	}
	securityParam = uint64(k)
	
	// Calculate 3k/f
	if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
		ls.config.Logger.Warn("ActiveSlotsCoeff.Rat is nil")
		return blockfetchBatchSlotThresholdDefault
	}
	
	result := new(big.Rat).Quo(
		big.NewRat(int64(3*securityParam), 1),
		shelleyGenesis.ActiveSlotsCoeff.Rat,
	)
	return new(big.Int).Div(result.Num(), result.Denom()).Uint64()
}

Then use it in both locations:

slotThreshold := ls.calculateStabilityWindow()

Also applies to: 298-314

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b47f49 and 67e2d75.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (1)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: lint
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
🔇 Additional comments (2)
ledger/state.go (1)

597-623: LGTM! Era-aware security parameter retrieval is correctly implemented.

The implementation properly handles era-specific security parameter sources with appropriate error handling and validation. The check for negative k before casting to uint64 (lines 616-621) is essential to prevent incorrect large values.

ledger/chainsync.go (1)

40-40: LGTM! Clear naming improvement.

Renaming to blockfetchBatchSlotThresholdDefault better conveys that this is a fallback value when genesis data is unavailable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
ledger/chainsync.go (1)

346-390: Same duplication and overflow issues as blockfetch threshold calculation.

This code segment duplicates the stability window calculation from lines 148-213. Please see my earlier comment on those lines suggesting extraction into a shared helper function.

Additionally, line 379 has the same potential uint64 overflow issue as line 193: computing 3 * securityParam before converting to big.Int could overflow if securityParam is large enough.

🧹 Nitpick comments (2)
ledger/state.go (1)

597-632: Suggest extracting era-aware security parameter retrieval into a helper function.

This security parameter retrieval logic (Byron K vs. Shelley SecurityParam with validation) is duplicated in ledger/chainsync.go at lines 148-172 and 346-362. Consider extracting it into a helper method on LedgerState, such as:

func (ls *LedgerState) getSecurityParam() (uint64, error) {
    if ls.currentEra.Id == 0 { // Byron era
        byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
        if byronGenesis == nil {
            return 0, errors.New("failed to get Byron Genesis config")
        }
        k := byronGenesis.ProtocolConsts.K
        if k < 0 {
            return 0, fmt.Errorf("Byron K parameter must be non-negative: %d", k)
        }
        return uint64(k), nil // #nosec G115
    } else { // Shelley and later eras
        shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
        if shelleyGenesis == nil {
            return 0, errors.New("failed to get Shelley Genesis config")
        }
        k := shelleyGenesis.SecurityParam
        if k < 0 {
            return 0, fmt.Errorf("security param must be non-negative: %d", k)
        }
        return uint64(k), nil // #nosec G115
    }
}

This would improve maintainability and ensure consistency across the codebase.

ledger/chainsync.go (1)

193-193: Apply defensive big.Int arithmetic for the security parameter multiplication to prevent theoretical uint64 overflow.

While Cardano protocol specifies no hardcoded maximum for the security parameter k and it's governance-changeable via on-chain updates, the current code multiplies 3 * securityParam in uint64 before converting to big.Int (ledger/chainsync.go lines 193 and 379). If securityParam ever exceeds ~6.1e18, this wraps silently and corrupts the stability window calculation—a bug the subsequent IsUint64() check won't catch.

Though mainnet k=2160 makes overflow practically impossible today, use big.Int throughout for robustness:

-numerator := new(big.Int).SetUint64(3 * securityParam)
+numerator := new(big.Int).SetUint64(securityParam)
+numerator.Mul(numerator, big.NewInt(3))

Apply this fix at both line 193 and line 379 in ledger/chainsync.go.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67e2d75 and 6734d13.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (1)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/chainsync.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: nilaway
  • GitHub Check: lint
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
ledger/chainsync.go (2)

39-40: LGTM!

The rename from blockfetchBatchSlotThreshold to blockfetchBatchSlotThresholdDefault clearly indicates this is a fallback value, which aligns well with the dynamic calculation logic introduced below.


215-219: LGTM!

The computed slotThreshold is correctly applied in the conditional to gate early return when the node is far from the upstream tip.

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 6734d13 to 8a15b9c Compare November 2, 2025 21:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ledger/state.go (1)

341-361: Avoid hard failure when Byron genesis is absent.

calculateEpochNonce already returns early for Byron (Line 327), so deployments that start in Shelley legitimately provide no Byron genesis. The new if byronGenesis == nil || shelleyGenesis == nil check makes those setups bail out with “could not get genesis config”, breaking epoch rollover. Please only require the genesis file for the era you actually use here.

Apply both hunks to keep compilation intact:

-	byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
-	shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
-	if byronGenesis == nil || shelleyGenesis == nil {
-		return nil, errors.New("could not get genesis config")
-	}
+	shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
+	if shelleyGenesis == nil {
+		return nil, errors.New("failed to get Shelley genesis config")
+	}
@@
-	if ls.currentEra.Id == 0 { // Byron era ID is 0
-		k := byronGenesis.ProtocolConsts.K
+	if ls.currentEra.Id == 0 { // Byron era ID is 0
+		byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
+		if byronGenesis == nil {
+			return nil, errors.New("failed to get Byron genesis config")
+		}
+		k := byronGenesis.ProtocolConsts.K
🧹 Nitpick comments (1)
ledger/chainsync.go (1)

144-213: Factor stability-window calculation into a helper.

This block duplicates the security-param extraction, ActiveSlotsCoeff validation, and overflow handling that calculateEpochNonce (Lines 346-390) already performs. Consider extracting a shared calculateStabilityWindow() helper returning (uint64, error) so both call sites stay in sync—here you can warn and fall back on error, while calculateEpochNonce can propagate it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6734d13 and 8a15b9c.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (1)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/chainsync.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: nilaway
  • GitHub Check: lint
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
ledger/state.go (1)

598-632: LGTM: era-aware k-window guard.

The non-negative checks and era split keep the cutoff aligned with the right security parameter without risking underflow.

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 8a15b9c to e3e3f6a Compare November 3, 2025 14:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a15b9c and e3e3f6a.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: lint
  • GitHub Check: nilaway
  • GitHub Check: go-test (1.25.x, macos-latest)
  • GitHub Check: go-test (1.24.x, macos-latest)
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from e3e3f6a to 471d3d0 Compare November 3, 2025 15:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3e3f6a and 471d3d0.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 471d3d0 to 42827a4 Compare November 3, 2025 16:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 471d3d0 and 42827a4.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 changed the title Use stability window instead of hard-coded slot threshold for blockfetch fix: use stability window for slot threshold Nov 3, 2025
@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 42827a4 to a2f1e1f Compare November 3, 2025 18:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ledger/state.go (1)

312-323: Ceil the Shelley-era stability window in epoch nonce calculation

The same floor-based division shows up here, so the epoch-nonce stability window can undershoot the spec by one slot on networks where 3*k/f is non-integral. That can change which block we treat as “before the stability window.” Please switch to the ceiling form as in the ledger reference.

-	numerator := new(big.Int).SetUint64(3 * securityParam)
-	quotient := new(big.Int).Div(
-		new(big.Int).Mul(numerator, activeSlotsRat.Denom()),
-		activeSlotsRat.Num(),
-	)
+	numerator := new(big.Int).SetUint64(securityParam)
+	numerator.Mul(numerator, big.NewInt(3))
+	numerator.Mul(numerator, activeSlotsRat.Denom())
+	denominator := new(big.Int).Set(activeSlotsRat.Num())
+	quotient, remainder := new(big.Int).QuoRem(numerator, denominator, new(big.Int))
+	if remainder.Sign() != 0 {
+		quotient.Add(quotient, big.NewInt(1))
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42827a4 and a2f1e1f.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from a2f1e1f to 594a6f6 Compare November 3, 2025 18:54
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
ledger/chainsync.go (1)

272-323: Consider calling calculateStabilityWindow() to reduce duplication.

The stability window calculation logic here (lines 272-323) closely mirrors the implementation in calculateStabilityWindow() (state.go lines 409-495). The only difference is error handling: this function returns errors while calculateStabilityWindow() logs and falls back to the default.

While the past review comment suggested extracting this into a shared helper, the current implementation keeps them separate due to different error-handling semantics. Consider whether you could call calculateStabilityWindow() and convert a zero return value into an error, which would eliminate the duplication while preserving the error-propagation behavior needed here.

For example:

stabilityWindow := ls.calculateStabilityWindow()
if stabilityWindow == blockfetchBatchSlotThresholdDefault {
    return nil, errors.New("could not calculate stability window from genesis config")
}

However, this approach loses granular error messages. If you prefer to keep the detailed validation errors, the current duplication is acceptable for maintainability of distinct error paths.

ledger/state.go (1)

685-720: Consider extracting era-aware security parameter retrieval into a helper.

The era-aware security parameter logic (lines 685-720) is duplicated in calculateEpochNonce (chainsync.go lines 272-295). Both branches check for genesis availability, validate non-negative parameters, and select between Byron K and Shelley SecurityParam based on era.

Extracting a helper like getSecurityParam() (uint64, error) would eliminate this duplication and ensure consistent validation across both call sites.

For example:

func (ls *LedgerState) getSecurityParam() (uint64, error) {
    if ls.currentEra.Id == 0 { // Byron era
        byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
        if byronGenesis == nil {
            return 0, errors.New("failed to get Byron Genesis config")
        }
        k := byronGenesis.ProtocolConsts.K
        if k <= 0 {
            return 0, fmt.Errorf("k parameter must be positive: %d", k)
        }
        return uint64(k), nil // #nosec G115
    }
    // Shelley+ eras
    shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
    if shelleyGenesis == nil {
        return 0, errors.New("failed to get Shelley Genesis config")
    }
    k := shelleyGenesis.SecurityParam
    if k <= 0 {
        return 0, fmt.Errorf("security param must be positive: %d", k)
    }
    return uint64(k), nil // #nosec G115
}

This is optional since the duplication is limited and the code is clear as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1e1f and 594a6f6.

📒 Files selected for processing (2)
  • ledger/chainsync.go (3 hunks)
  • ledger/state.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
ledger/chainsync.go (2)

39-40: LGTM: Constant rename clarifies fallback behavior.

The rename from blockfetchBatchSlotThreshold to blockfetchBatchSlotThresholdDefault accurately reflects that this is now a fallback value when genesis data is unavailable or invalid.


143-146: LGTM: Dynamic stability window calculation properly integrated.

The block header handling now uses the calculated stability window instead of a hard-coded value, achieving the PR's objective of protocol compliance and era awareness.

ledger/state.go (1)

409-495: LGTM: Comprehensive stability window calculation with proper safeguards.

The calculateStabilityWindow() method correctly implements:

  • Era-aware parameter selection (Byron K vs. Shelley SecurityParam)
  • Validation of genesis availability, non-negative/non-zero parameters, and ActiveSlotsCoeff
  • Ceiling calculation for Shelley+ eras (3k/f rounded up) using proper big.Int arithmetic
  • Overflow checks before uint64 conversion
  • Fallback to default threshold on any validation failure

All issues raised in past review comments have been addressed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Creating a local commit for the generated unit tests...

The unit tests have been committed to the current branch. Commit ID: a43d73e32d77005b8841b7f138d5e093f883d3dd

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
ledger/chainsync.go (1)

296-323: Consolidate stability window calculation

calculateEpochNonce re-implements the stability-window math even though handleEventChainsyncBlockHeader now relies on ls.calculateStabilityWindow(). Keeping two hand-written copies makes it easy for the formulas or validations to drift apart the next time we tweak era handling or overflow checks. Please fold both call sites onto the same helper (or extract a shared internal routine that returns (uint64, error) so calculateEpochNonce can surface hard failures) and have the caller decide whether to log/fallback or bubble the error. That keeps the protocol constants in one place and prevents future regressions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 594a6f6 and b73183f.

📒 Files selected for processing (1)
  • ledger/chainsync.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (1)
  • Rat (25-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from a43d73e to 3d97e2c Compare November 3, 2025 19:20
@wolf31o2
Copy link
Member Author

wolf31o2 commented Nov 3, 2025

@coderabbitai the tests for this are failing

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Run the tests to see what's failing
cd ledger
go test -v -run "Test" 2>&1 | head -200

Length of output: 12045


🏁 Script executed:

#!/bin/bash
# Get more test output to see the failures
cd ledger
go test -v -run "Test" 2>&1 | tail -400

Length of output: 4717


🏁 Script executed:

#!/bin/bash
# Look at the failing test code
cat ledger/chainsync_test.go | sed -n '680,720p'

Length of output: 889


🏁 Script executed:

#!/bin/bash
# Look at the calculateEpochNonce implementation to find the nil pointer issue
cat ledger/chainsync.go | sed -n '1,50p'

Length of output: 1649


🏁 Script executed:

#!/bin/bash
# Find and display the calculateEpochNonce function
ast-grep --pattern $'func (ls *LedgerState) calculateEpochNonce($$$) $$$'

Length of output: 6322


🏁 Script executed:

#!/bin/bash
# Look at the calculateStabilityWindow function in state.go
ast-grep --pattern $'func (ls *LedgerState) calculateStabilityWindow() $$$'

Length of output: 4114


🏁 Script executed:

#!/bin/bash
# Let me see the full test function to understand the intent
cat ledger/chainsync_test.go | sed -n '670,710p'

Length of output: 1216


🏁 Script executed:

#!/bin/bash
# Check if there are any test failures in state_test.go
cd ledger
go test -v -run "TestCalculateStabilityWindow" 2>&1

Length of output: 4951


@wolf31o2, I found the issue! The test TestCalculateEpochNonce_MissingByronGenesisInByronEra has incorrect expectations.

The problem is that in Byron era (era ID 0), the calculateEpochNonce function returns immediately at line 257-259 with nil, nil because Byron doesn't have epoch nonces. This early return happens before any genesis validation, so missing Byron genesis doesn't cause an error in this code path.

The test expects an error but gets nil, then tries to call err.Error() on line 703, causing the nil pointer panic.

Here's the fix for the test:

func TestCalculateEpochNonce_MissingByronGenesisInByronEra(t *testing.T) {
	shelleyGenesisJSON := `{
		"activeSlotsCoeff": 0.05,
		"securityParam": 432,
		"systemStart": "2022-10-25T00:00:00Z"
	}`

	cfg := &cardano.CardanoNodeConfig{
		ShelleyGenesisHash: "363498d1024f84bb39d3fa9593ce391483cb40d479b87233f868d6e57c3a400d",
	}
	if err := cfg.LoadShelleyGenesisFromReader(strings.NewReader(shelleyGenesisJSON)); err != nil {
		t.Fatalf("failed to load Shelley genesis: %v", err)
	}

	ls := &LedgerState{
		currentEra: eras.ByronEraDesc,
		currentEpoch: models.Epoch{
			EpochId:   1,
			StartSlot: 86400,
			Nonce:     []byte{0x01, 0x02, 0x03},
		},
		config: LedgerStateConfig{
			CardanoNodeConfig: cfg,
			Logger:            slog.New(slog.NewJSONHandler(io.Discard, nil)),
		},
		db: &database.Database{},
	}

	// Byron era should return nil nonce with no error (Byron doesn't have epoch nonces)
	// The function returns early before any genesis validation
	nonce, err := ls.calculateEpochNonce(nil, 86400)
	if err != nil {
		t.Errorf("expected no error for Byron era, got: %v", err)
	}
	if nonce != nil {
		t.Errorf("expected nil nonce for Byron era, got: %v", nonce)
	}
}

The fix changes the test to correctly verify that:

  1. No error is returned for Byron era (even with missing Byron genesis)
  2. Nonce is nil for Byron era (as Byron doesn't use epoch nonces)

This aligns with the actual behavior of the code where Byron era returns immediately without needing any genesis data.


🧠 Learnings used
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 3d97e2c to ad2f39f Compare November 3, 2025 21:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d97e2c and ad2f39f.

📒 Files selected for processing (4)
  • ledger/chainsync.go (3 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (2 hunks)
  • ledger/state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ledger/chainsync_test.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state_test.go
🧬 Code graph analysis (3)
ledger/chainsync.go (2)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/state_test.go (5)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from ad2f39f to 4a59bd9 Compare November 3, 2025 22:59
@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 4a59bd9 to 3dccfa3 Compare November 5, 2025 19:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a59bd9 and 3dccfa3.

📒 Files selected for processing (4)
  • ledger/chainsync.go (3 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ledger/chainsync_test.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/chainsync.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state_test.go
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
ledger/state_test.go (9)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/allegra.go (1)
  • AllegraEraDesc (30-41)
ledger/eras/mary.go (1)
  • MaryEraDesc (30-41)
ledger/eras/alonzo.go (1)
  • AlonzoEraDesc (30-41)
ledger/eras/babbage.go (1)
  • BabbageEraDesc (30-41)
ledger/eras/conway.go (1)
  • ConwayEraDesc (33-45)
🪛 GitHub Actions: go-test
ledger/chainsync.go

[error] 278-278: ledgerState.calculateEpochNonce panicked due to nil Shelley genesis during TestCalculateEpochNonce_MissingShelleyGenesis.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: nilaway
  • GitHub Check: lint
  • GitHub Check: Analyze (go)

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 294d4ef to 23e4983 Compare November 5, 2025 20:15
@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 23e4983 to ac1e8c9 Compare November 5, 2025 20:23
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dccfa3 and ac1e8c9.

📒 Files selected for processing (4)
  • ledger/chainsync.go (3 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ledger/chainsync.go
  • ledger/state_test.go
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.

Applied to files:

  • ledger/chainsync_test.go
🧬 Code graph analysis (2)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/chainsync_test.go (6)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
database/database.go (1)
  • Database (45-50)
🪛 GitHub Actions: go-test
ledger/chainsync_test.go

[error] 208-208: TestCalculateEpochNonce_MissingShelleyGenesis failed with panic: runtime error: invalid memory address or nil pointer dereference

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
  • GitHub Check: lint

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from ac1e8c9 to fe8d7d7 Compare November 6, 2025 18:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
ledger/state.go (1)

420-437: Apply fix to Byron stability window: use 2k, not k per Ouroboros specification.

Byron's stability window specification is 2k slots per Ouroboros consensus documentation. The current implementation returns k directly (line 436), contradicting the spec. Tests currently validate the incorrect behavior (expecting k=2160 instead of 2k=4320).

Apply this fix:

 		// Byron uses k directly
-		return uint64(k) // #nosec G115
+		return uint64(k) * 2 // Byron stability window is 2k slots

Also update the function comment (line 410) to reflect the corrected behavior:

-// For Byron era, returns k. For Shelley+ eras, returns 3k/f.
+// For Byron era, returns 2k. For Shelley+ eras, returns 3k/f.

Update test expectations in ledger/state_test.go to validate 2k instead of k.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac1e8c9 and fe8d7d7.

📒 Files selected for processing (4)
  • ledger/chainsync.go (3 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state_test.go
  • ledger/chainsync_test.go
  • ledger/state.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state_test.go
  • ledger/chainsync_test.go
  • ledger/state.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state_test.go
  • ledger/chainsync_test.go
  • ledger/state.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/chainsync_test.go
  • ledger/state.go
  • ledger/chainsync.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/chainsync_test.go
  • ledger/state.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/chainsync_test.go
  • ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/chainsync_test.go
  • ledger/state.go
🧬 Code graph analysis (3)
ledger/state_test.go (5)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
ledger/chainsync_test.go (11)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
ledger/eras/allegra.go (1)
  • AllegraEraDesc (30-41)
ledger/eras/mary.go (1)
  • MaryEraDesc (30-41)
ledger/eras/alonzo.go (1)
  • AlonzoEraDesc (30-41)
ledger/eras/babbage.go (1)
  • BabbageEraDesc (30-41)
ledger/eras/conway.go (1)
  • ConwayEraDesc (33-45)
database/database.go (1)
  • Database (45-50)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
🪛 GitHub Actions: go-test
ledger/chainsync_test.go

[error] 210-210: TestCalculateEpochNonce_MissingShelleyGenesis: expected error for missing Shelley genesis, got nil


[error] 379-379: TestCalculateEpochNonce_ZeroActiveSlots: panic caused by runtime error: nil pointer dereference during epoch nonce calculation

ledger/chainsync.go

[error] 278-278: ledger state calculation failed due to nil pointer dereference when accessing block/txn (BlockBeforeSlotTxn called with nil Txn).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
ledger/chainsync.go (2)

38-39: LGTM! Dynamic stability window successfully integrated.

The replacement of the hard-coded threshold with a dynamic per-era calculation via calculateStabilityWindow() achieves the PR objective of using protocol-defined stability windows.

Also applies to: 143-143, 145-145


269-276: Verify transaction parameter in test callers.

The stability window calculation correctly uses calculateStabilityWindow() as suggested in past reviews. However, when currentEpoch.Nonce is non-empty, this code path requires database access via BlockBeforeSlotTxn(txn, ...) at line 278.

Tests calling calculateEpochNonce(nil, ...) with a non-empty nonce will panic with a nil pointer dereference, which matches the pipeline failure. The test setup issue should be addressed in the test file.

Production code is correct; the issue is in test setup (see comments on ledger/chainsync_test.go).

Also applies to: 278-281

ledger/state_test.go (1)

233-259: LGTM! Test correctly expects Byron security parameter.

This test was correctly updated per past review feedback (commit 3dccfa3) to expect the Byron security parameter (432) rather than the default threshold when Shelley genesis is missing but Byron genesis is present during Byron era.

Based on learnings.

ledger/state.go (1)

439-503: LGTM! Shelley+ stability window calculation is robust.

The implementation correctly:

  • Validates Shelley genesis presence
  • Checks for non-positive security parameter
  • Guards against nil/non-positive ActiveSlotsCoeff
  • Uses ceiling arithmetic (QuoRem + rounding up) for 3k/f
  • Validates the result fits in uint64
  • Falls back to default threshold on any validation failure

All issues raised in past reviews have been addressed.

Comment on lines 342 to 377
func TestCalculateEpochNonce_ZeroActiveSlots(t *testing.T) {
byronGenesisJSON := `{
"protocolConsts": {
"k": 432,
"protocolMagic": 2
}
}`
shelleyGenesisJSON := `{
"activeSlotsCoeff": 0,
"securityParam": 432,
"systemStart": "2022-10-25T00:00:00Z"
}`

cfg := &cardano.CardanoNodeConfig{
ShelleyGenesisHash: "363498d1024f84bb39d3fa9593ce391483cb40d479b87233f868d6e57c3a400d",
}
if err := cfg.LoadByronGenesisFromReader(strings.NewReader(byronGenesisJSON)); err != nil {
t.Fatalf("failed to load Byron genesis: %v", err)
}
if err := cfg.LoadShelleyGenesisFromReader(strings.NewReader(shelleyGenesisJSON)); err != nil {
t.Fatalf("failed to load Shelley genesis: %v", err)
}

ls := &LedgerState{
currentEra: eras.ShelleyEraDesc,
currentEpoch: models.Epoch{
EpochId: 1,
StartSlot: 86400,
Nonce: []byte{0x01, 0x02, 0x03},
},
config: LedgerStateConfig{
CardanoNodeConfig: cfg,
Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)),
},
}

// Zero active slots coefficient should cause division by zero error
_, err := ls.calculateEpochNonce(nil, 86400)
if err == nil {
t.Error("expected error for zero active slots coefficient, got nil")
}
if !strings.Contains(err.Error(), "divide by zero") &&
!strings.Contains(err.Error(), "ActiveSlotsCoeff") {
t.Errorf(
"expected error about division by zero or ActiveSlotsCoeff, got: %v",
err,
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix test: provide valid transaction or adjust test setup.

This test panics because it passes nil for the txn parameter (line 379) but sets a non-empty Nonce (line 370). With a non-empty nonce, calculateEpochNonce skips the initial epoch path and attempts database queries via BlockBeforeSlotTxn(txn, ...), causing a nil pointer dereference.

To fix, either:

  1. Preferred: Create a test database and pass a valid transaction:
+	db := database.NewTestDatabase(t) // or appropriate test DB setup
+	ls.db = db
+	txn := db.Transaction(false)
+	err := txn.Do(func(txn *database.Txn) error {
-		_, err := ls.calculateEpochNonce(nil, 86400)
+		_, err := ls.calculateEpochNonce(txn, 86400)
+		return err
+	})
-	if err == nil {
+	if err == nil || txn.Err() == nil {
 		t.Error("expected error for zero active slots coefficient, got nil")
 	}
  1. Alternative: Set Nonce to nil so the test takes the initial epoch path that doesn't require database access (but then it won't test the stability window calculation).

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: go-test

[error] 379-379: TestCalculateEpochNonce_ZeroActiveSlots: panic caused by runtime error: nil pointer dereference during epoch nonce calculation

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from fe8d7d7 to d93f575 Compare November 6, 2025 18:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe8d7d7 and d93f575.

📒 Files selected for processing (4)
  • ledger/chainsync.go (4 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
🧬 Code graph analysis (4)
ledger/chainsync.go (1)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (2)
  • Rat (25-27)
  • Uint64 (54-54)
ledger/chainsync_test.go (6)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
database/database.go (1)
  • Database (45-50)
ledger/state_test.go (10)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
ledger/eras/allegra.go (1)
  • AllegraEraDesc (30-41)
ledger/eras/mary.go (1)
  • MaryEraDesc (30-41)
ledger/eras/alonzo.go (1)
  • AlonzoEraDesc (30-41)
ledger/eras/babbage.go (1)
  • BabbageEraDesc (30-41)
ledger/eras/conway.go (1)
  • ConwayEraDesc (33-45)
🪛 GitHub Actions: go-test
ledger/chainsync_test.go

[error] 372-372: TestCalculateEpochNonce_ZeroActiveSlots: expected error for zero active slots coefficient, got nil


[error] 374-374: panic: runtime error: invalid memory address or nil pointer dereference

🪛 GitHub Actions: golangci-lint
ledger/state.go

[error] 436-436: File is not properly formatted (gci).

🪛 GitHub Check: lint
ledger/state.go

[failure] 436-436:
File is not properly formatted (gci)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: nilaway

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 47b1ad0 to 9bb8bc1 Compare November 6, 2025 19:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
ledger/chainsync_test.go (3)

217-257: Strengthen test assertions or clarify purpose.

This test has weak assertions and doesn't effectively validate negative security parameter handling:

  1. Genesis loading errors are ignored (lines 234-235), so if negative k causes loading to fail, the test doesn't detect it.
  2. Byron era returns (nil, nil) immediately regardless of genesis validity, so calculateEpochNonce never validates the negative k.
  3. The only assertion is a log statement when no error occurs (line 255).

Consider either:

  • If testing genesis loading: Assert that LoadByronGenesisFromReader returns an error for negative k.
  • If testing calculateEpochNonce: Use Shelley era with appropriate setup to exercise the validation path.
  • If negative k is actually valid: Document why and what behavior is expected.

Apply this diff to test genesis loading validation:

-	_ = cfg.LoadByronGenesisFromReader(strings.NewReader(byronGenesisJSON))
-	_ = cfg.LoadShelleyGenesisFromReader(strings.NewReader(shelleyGenesisJSON))
-
-	ls := &LedgerState{
-		currentEra: eras.ByronEraDesc,
-		currentEpoch: models.Epoch{
-			EpochId:   1,
-			StartSlot: 86400,
-			Nonce:     []byte{0x01, 0x02, 0x03},
-		},
-		config: LedgerStateConfig{
-			CardanoNodeConfig: cfg,
-			Logger:            slog.New(slog.NewJSONHandler(io.Discard, nil)),
-		},
-	}
-
-	// Test will depend on whether the genesis loads successfully
-	// If it loads, we expect an error about negative k
-	_, err := ls.calculateEpochNonce(nil, 86400)
-	// Either genesis loading fails or calculateEpochNonce catches negative k
-	if err == nil {
-		t.Log("Note: negative k may be caught during genesis loading")
-	}
+	err := cfg.LoadByronGenesisFromReader(strings.NewReader(byronGenesisJSON))
+	if err == nil {
+		t.Error("expected error when loading Byron genesis with negative k")
+	}
+	// Alternatively, if negative k is allowed, document the expected behavior

523-569: Consider clarifying test name to match implementation.

The test correctly validates that calculateStabilityWindow() returns the protocol-derived value (25920) instead of the hardcoded constant. However, the test name suggests it tests handleEventChainsyncBlockHeader but only directly calls calculateStabilityWindow(). Consider renaming to TestCalculateStabilityWindow_ShelleyEra for clarity, or add a comment explaining that this validates the value used by the handler.


684-721: LGTM: Byron era early return correctly validated.

The test properly verifies that Byron era returns nil immediately without validating genesis, as corrected in the referenced commit. The non-nil nonce (line 704) and database (line 710) setup are unused due to the early return but don't cause issues.

Optional cleanup to simplify the test:

 	ls := &LedgerState{
 		currentEra: eras.ByronEraDesc,
 		currentEpoch: models.Epoch{
 			EpochId:   1,
 			StartSlot: 86400,
-			Nonce:     []byte{0x01, 0x02, 0x03},
+			Nonce:     nil,
 		},
 		config: LedgerStateConfig{
 			CardanoNodeConfig: cfg,
 			Logger:            slog.New(slog.NewJSONHandler(io.Discard, nil)),
 		},
-		db: &database.Database{},
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d93f575 and 47b1ad0.

📒 Files selected for processing (1)
  • ledger/chainsync_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.

Applied to files:

  • ledger/chainsync_test.go
🧬 Code graph analysis (1)
ledger/chainsync_test.go (6)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
database/database.go (1)
  • Database (45-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: nilaway
  • GitHub Check: go-test (1.24.x, macos-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (9)
ledger/chainsync_test.go (9)

32-77: LGTM: Byron era test correctly validates early return behavior.

The test properly validates that Byron era returns nil nonce without error, which aligns with the early return in calculateEpochNonce.


79-145: LGTM: Initial epoch test properly validates genesis hash return.

The test correctly verifies that an initial Shelley epoch (nil nonce) returns the Shelley genesis hash, with thorough byte-by-byte validation.


147-189: LGTM: Invalid genesis hash test correctly expects error.

The test properly validates that an invalid hex string in the genesis hash produces an error during epoch nonce calculation.


191-215: LGTM: Missing genesis test correctly validates error handling.

The test properly validates that missing Shelley genesis hash produces an appropriate error, as fixed in the referenced commit.


259-339: LGTM: Comprehensive parameter variation coverage.

The table-driven test effectively validates epoch nonce calculation across different security parameters and active slots coefficients, covering testnet, mainnet, and high-activity scenarios.


341-374: LGTM: Zero active slots test correctly validates fallback behavior.

The test properly verifies that calculateStabilityWindow() returns the fallback threshold when ActiveSlotsCoeff is zero, avoiding the nil transaction issue from previous iterations.


376-473: LGTM: Stability window calculation test with clear formula documentation.

The test effectively validates stability window calculations across eras with well-documented expected formulas, providing good traceability for the 3k/f calculation.


475-521: LGTM: Integer arithmetic precision test covers fractional coefficient edge case.

The test properly validates that the implementation handles fractional active slots coefficients correctly using integer arithmetic, catching potential rounding or precision issues.


571-682: LGTM: Comprehensive cross-era validation.

The test provides excellent coverage across all Cardano eras (Byron through Conway), validating the era-aware behavior that's central to this PR's objectives.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
ledger/state_test.go (1)

494-498: Fix Byron era expectation and comment.

The Byron era test case expects k (432) but the implementation returns 2k (864). The comment "Just k" is incorrect—Byron uses 2k per the consensus specification and the implementation at line 436 in state.go.

 		{
 			name:           "Byron era",
 			era:            eras.ByronEraDesc,
-			expectedWindow: 432, // Just k
+			expectedWindow: 864, // Byron uses 2k
 		},
🧹 Nitpick comments (1)
ledger/state.go (1)

435-435: Clean up redundant comment.

The comment has "// Byron //" which appears to be a duplicate or merge artifact. Remove the redundant "Byron" text.

-		// Byron // Byron stability window is 2k slots
+		// Byron stability window is 2k slots
 		return uint64(k)*2 // #nosec G115
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47b1ad0 and 9bb8bc1.

📒 Files selected for processing (4)
  • ledger/chainsync.go (4 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ledger/chainsync.go
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state_test.go
  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state_test.go
  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state_test.go
  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state_test.go
  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state_test.go
  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.

Applied to files:

  • ledger/chainsync_test.go
🧬 Code graph analysis (3)
ledger/state_test.go (10)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/byron.go (1)
  • ByronEraDesc (24-28)
ledger/eras/shelley.go (1)
  • ShelleyEraDesc (30-41)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
ledger/eras/allegra.go (1)
  • AllegraEraDesc (30-41)
ledger/eras/mary.go (1)
  • MaryEraDesc (30-41)
ledger/eras/alonzo.go (1)
  • AlonzoEraDesc (30-41)
ledger/eras/babbage.go (1)
  • BabbageEraDesc (30-41)
ledger/eras/conway.go (1)
  • ConwayEraDesc (33-45)
ledger/state.go (3)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
database/plugin/log.go (1)
  • Logger (18-27)
database/types/types.go (1)
  • Rat (25-27)
ledger/chainsync_test.go (4)
config/cardano/node.go (1)
  • CardanoNodeConfig (31-45)
ledger/state.go (2)
  • LedgerState (78-102)
  • LedgerStateConfig (58-68)
ledger/eras/eras.go (1)
  • EraDesc (23-35)
database/database.go (1)
  • Database (45-50)
🪛 GitHub Actions: go-test
ledger/state_test.go

[error] 93-93: TestCalculateStabilityWindow_ByronEra/Byron_era_with_k=1: expected stability window 1, got 2


[error] 257-257: TestCalculateStabilityWindow_EdgeCases/Missing_Shelley_genesis_returns_default: expected default threshold 432, got 864


[error] 545-545: TestCalculateStabilityWindow_AllEras/Byron_era: era Byron: expected stability window 432, got 864


[error] 592-592: TestCalculateStabilityWindow_Integration/Mainnet-like_configuration: Byron era: expected stability window 2160, got 4320

🪛 GitHub Actions: golangci-lint
ledger/state.go

[error] 436-436: golangci-lint: File is not properly formatted (gci)

🪛 GitHub Check: lint
ledger/state.go

[failure] 436-436:
File is not properly formatted (gci)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
ledger/chainsync_test.go (1)

1-721: Excellent test coverage for epoch nonce calculations.

This test suite provides comprehensive coverage of epoch nonce calculation logic across all Cardano eras (Byron through Conway), including edge cases for missing or invalid genesis data, zero parameters, and precision handling. The tests are well-organized and clearly demonstrate the expected behavior for each scenario.

ledger/state.go (1)

436-436: Review comment is incorrect—gci only checks import formatting.

Line 436 contains return uint64(k)*2 // #nosec G115, which is a return statement in function code. The gci formatter only validates import block ordering and grouping; it does not check executable code statements. This review comment cannot be referring to gci formatting issues on that line.

Likely an incorrect or invalid review comment.

@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 9bb8bc1 to 8825574 Compare November 6, 2025 19:31
@wolf31o2
Copy link
Member Author

wolf31o2 commented Nov 6, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
ledger/state_test.go (3)

47-50: Fix Byron stability window expectation for k=1.

The test expects 1 but calculateStabilityWindow() returns 2*k for Byron era (line 436 in state.go). Update the expected value to 2.

Apply this diff:

 		{
 			name:           "Byron era with k=1",
 			k:              1,
-			expectedWindow: 1,
+			expectedWindow: 2,
 		},

233-259: Fix Byron stability window expectation when Shelley genesis is missing.

This test expects 432 (k) when only Byron genesis is present, but calculateStabilityWindow() returns 2*k for Byron era (line 436 in state.go). With k=432, the result is 864.

Apply this diff:

 		result := ls.calculateStabilityWindow()
-		if result != 432 {
-			t.Errorf("expected default threshold %d, got %d", 432, result)
+		if result != 864 {
+			t.Errorf("expected Byron stability window (2*k) %d, got %d", 864, result)
 		}

579-596: Fix mainnet Byron stability window expectation.

The test expects 2160 (k) for mainnet Byron parameters, but the implementation returns 2*k. With k=2160, the Byron stability window is 4320 slots.

Apply this diff:

 		resultByron := lsByron.calculateStabilityWindow()
-		if resultByron != 2160 {
+		if resultByron != 4320 {
 			t.Errorf(
-				"Byron era: expected stability window 2160, got %d",
+				"Byron era: expected stability window 4320 (2*k), got %d",
 				resultByron,
 			)
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb8bc1 and 8825574.

📒 Files selected for processing (4)
  • ledger/chainsync.go (4 hunks)
  • ledger/chainsync_test.go (1 hunks)
  • ledger/state.go (4 hunks)
  • ledger/state_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
  • ledger/state_test.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.

Applied to files:

  • ledger/state.go
  • ledger/chainsync_test.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.

Applied to files:

  • ledger/chainsync_test.go
🪛 GitHub Actions: go-test
ledger/chainsync_test.go

[error] 26-26: "github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Actions: golangci-lint
ledger/state.go

[error] 436-436: golangci-lint: File is not properly formatted (gci)

🪛 GitHub Actions: nilaway
ledger/chainsync_test.go

[error] 26-26: "github.com/blinklabs-io/dingo/database" imported and not used. (from build/test step: nilaway ./... || true)

🪛 GitHub Check: go-test (1.25.x, macos-latest)
ledger/chainsync_test.go

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Check: go-test (1.25.x, ubuntu-latest)
ledger/chainsync_test.go

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Check: lint
ledger/state.go

[failure] 436-436:
File is not properly formatted (gci)

🪛 GitHub Check: nilaway
ledger/chainsync_test.go

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🔇 Additional comments (3)
ledger/chainsync.go (2)

142-145: LGTM! Dynamic stability window correctly implemented.

The replacement of the hard-coded threshold with a call to calculateStabilityWindow() successfully achieves the PR's objective of deriving the blockfetch slot threshold from protocol parameters.


261-263: LGTM! Genesis hash validation correctly added.

The validation ensures that an empty ShelleyGenesisHash is caught before attempting to decode it, preventing the test failure that was identified in previous reviews.

ledger/state.go (1)

439-504: LGTM! Shelley+ stability window calculation is correct.

The implementation properly:

  • Validates all genesis parameters before use
  • Computes the ceiling of 3k/f using correct integer arithmetic
  • Guards against overflow and underflow
  • Falls back to the default threshold on any validation failure

"testing"

"github.com/blinklabs-io/dingo/config/cardano"
"github.com/blinklabs-io/dingo/database"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove unused database import.

The database package is imported but never used in this test file. This is causing pipeline failures.

Apply this diff:

 	"github.com/blinklabs-io/dingo/config/cardano"
-	"github.com/blinklabs-io/dingo/database"
 	"github.com/blinklabs-io/dingo/database/models"
 	"github.com/blinklabs-io/dingo/ledger/eras"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"github.com/blinklabs-io/dingo/database"
"github.com/blinklabs-io/dingo/config/cardano"
"github.com/blinklabs-io/dingo/database/models"
"github.com/blinklabs-io/dingo/ledger/eras"
🧰 Tools
🪛 GitHub Actions: go-test

[error] 26-26: "github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Actions: nilaway

[error] 26-26: "github.com/blinklabs-io/dingo/database" imported and not used. (from build/test step: nilaway ./... || true)

🪛 GitHub Check: go-test (1.25.x, macos-latest)

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Check: go-test (1.25.x, ubuntu-latest)

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🪛 GitHub Check: nilaway

[failure] 26-26:
"github.com/blinklabs-io/dingo/database" imported and not used

🤖 Prompt for AI Agents
In ledger/chainsync_test.go around line 26, the import
"github.com/blinklabs-io/dingo/database" is unused and causing build failures;
remove that import from the file's import block (or reference it if
intentionally needed), then run `go test` to confirm the pipeline error is
resolved.

- Replace hard-coded blockfetchBatchSlotThreshold with dynamic calculation
- Use correct security parameters based on current era:
  * Byron era: K parameter from ByronGenesis.ProtocolConsts.K
  * Shelley+ eras: SecurityParam from ShelleyGenesis.SecurityParam
- Update stability window calculation in both chainsync and validation logic
- Calculate stability window as 3k/f for Shelley+ eras, k for Byron era
- Ensures blockfetch operations respect protocol-defined stability window

Fixes the TODO to calculate slot threshold from protocol params

Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@wolf31o2 wolf31o2 force-pushed the feature/use-stability-window-for-blockfetch branch from 8825574 to c2ab4d2 Compare November 6, 2025 20:37
@wolf31o2 wolf31o2 merged commit 3b71728 into main Nov 6, 2025
12 checks passed
@wolf31o2 wolf31o2 deleted the feature/use-stability-window-for-blockfetch branch November 6, 2025 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Calculate blockfetch batch slot threshold from protocol params

3 participants